package mutsumimod.patches;

import com.evacipated.cardcrawl.modthespire.lib.*;
import com.evacipated.cardcrawl.modthespire.patcher.PatchingException;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.characters.AbstractPlayer;
import com.megacrit.cardcrawl.core.AbstractCreature;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import com.megacrit.cardcrawl.powers.AbstractPower;
import javassist.CannotCompileException;
import javassist.CtBehavior;
import mutsumimod.monitors.ForgetMonitor;
import mutsumimod.monitors.SuperMetalMonitor;
import mutsumimod.powers.BasePower;
import mutsumimod.powers.BreakPower;
import mutsumimod.powers.CrossingSeaPower;

import java.util.ArrayList;

public class BreakPowerPatch {

    @SpirePatch(
            clz = AbstractCreature.class,
            method = SpirePatch.CLASS
    )
    public static class AbstractCreatureFieldPatch {
        public static SpireField<Integer> trueLastDamageTaken = new SpireField<>(() -> -1);
    }

    @SpirePatch(
            clz = AbstractMonster.class,
            method = "damage",
            paramtypez = {
                    DamageInfo.class
            }
    )
    public static class MonsterBreakPatch {
        @SpireInsertPatch(
                locator = Locator.class,
                localvars = {"damageAmount"}
        )
        public static void insert(AbstractMonster __instance,DamageInfo info,@ByRef int[] damageAmount){
            if(__instance.hasPower(BreakPower.POWER_ID)){
                BreakPower power = (BreakPower)__instance.getPower(BreakPower.POWER_ID);
                damageAmount[0] =power.afterAttackedToChangeDamage(info,damageAmount[0]);
            }
        }

        private static class Locator extends SpireInsertLocator {
            public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
                Matcher finalMatcher = new Matcher.MethodCallMatcher(AbstractMonster.class, "decrementBlock");

                int[] lines = LineFinder.findInOrder(ctMethodToPatch, new ArrayList<Matcher>(), finalMatcher);
                return new int[]{lines[0]+1};
            }
        }
    }

    @SpirePatch(
            clz = AbstractMonster.class,
            method = "damage",
            paramtypez = {
                    DamageInfo.class
            }
    )
    public static class MonsterGetTureDamagePatch {
        @SpireInsertPatch(
                locator = Locator.class,
                localvars = {"damageAmount"}
        )
        public static void insert(AbstractMonster __instance,DamageInfo info,@ByRef int[] damageAmount){
            if(__instance.hasPower(BreakPower.POWER_ID)){
                BreakPower power = (BreakPower)__instance.getPower(BreakPower.POWER_ID);
                power.loseAmount(info,damageAmount[0]);
            }
            for(AbstractPower p : __instance.powers){
                if(p instanceof BasePower){
                    ((BasePower) p).betterOnLoseHp(info,damageAmount[0]);
                }
            }
            for(AbstractPower p : AbstractDungeon.player.powers){
                if(p instanceof BasePower){
                    ((BasePower) p).getTrueDamageAmount(info,damageAmount[0]);
                }
            }
            AbstractCreatureFieldPatch.trueLastDamageTaken.set(__instance,damageAmount[0]);
            SuperMetalMonitor.maxDamage=Math.max(damageAmount[0], SuperMetalMonitor.maxDamage);

        }

        private static class Locator extends SpireInsertLocator {
            public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
                Matcher finalMatcher = new Matcher.FieldAccessMatcher(AbstractMonster.class, "lastDamageTaken");

                int[] lines = LineFinder.findInOrder(ctMethodToPatch, new ArrayList<Matcher>(), finalMatcher);
                return new int[]{lines[0]};
            }
        }
    }

    public static boolean shouldUseBreakPower(AbstractCreature owner) {
        return owner.hasPower(BreakPower.POWER_ID)&&!AbstractDungeon.player.hasPower(CrossingSeaPower.POWER_ID);
    }

    @SpirePatch(
            clz = AbstractPlayer.class,
            method = "damage",
            paramtypez = {
                    DamageInfo.class
            }
    )
    public static class PlayerBreakPatch {
        @SpireInsertPatch(
                locator = Locator.class,
                localvars = {"damageAmount"}
        )
        public static void insert(AbstractPlayer __instance,DamageInfo info,@ByRef int[] damageAmount){
            if(shouldUseBreakPower(__instance)){
                BreakPower power = (BreakPower)__instance.getPower(BreakPower.POWER_ID);
                damageAmount[0] =power.afterAttackedToChangeDamage(info,damageAmount[0]);
            }
        }

        private static class Locator extends SpireInsertLocator {
            public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
                Matcher finalMatcher = new Matcher.MethodCallMatcher(AbstractPlayer.class, "decrementBlock");

                int[] lines = LineFinder.findInOrder(ctMethodToPatch, new ArrayList<Matcher>(), finalMatcher);
                return new int[]{lines[0]+1};
            }
        }
    }

    @SpirePatch(
            clz = AbstractPlayer.class,
            method = "damage",
            paramtypez = {
                    DamageInfo.class
            }
    )
    public static class PlayerDamagePatch {
        @SpireInsertPatch(
                locator = Locator.class,
                localvars = {"damageAmount"}
        )
        public static void insert(AbstractPlayer __instance,DamageInfo info,@ByRef int[] damageAmount){
            if(__instance.hasPower(BreakPower.POWER_ID)){
                BreakPower power = (BreakPower)__instance.getPower(BreakPower.POWER_ID);
                power.loseAmount(info,damageAmount[0]);
            }
            for(AbstractPower p : __instance.powers){
                if(p instanceof BasePower){
                    ((BasePower) p).betterOnLoseHp(info,damageAmount[0]);
                }
            }
            ForgetMonitor.addDamageThisTurn(damageAmount[0]);
            AbstractCreatureFieldPatch.trueLastDamageTaken.set(__instance,damageAmount[0]);
            SuperMetalMonitor.maxDamage=Math.max(damageAmount[0], SuperMetalMonitor.maxDamage);
        }

        private static class Locator extends SpireInsertLocator {
            public int[] Locate(CtBehavior ctMethodToPatch) throws CannotCompileException, PatchingException {
                Matcher finalMatcher = new Matcher.FieldAccessMatcher(AbstractPlayer.class, "lastDamageTaken");

                int[] lines = LineFinder.findInOrder(ctMethodToPatch, new ArrayList<Matcher>(), finalMatcher);
                return new int[]{lines[0]};
            }
        }
    }
}
